// Encoder.cpp: implementation of the CEncoder class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Encoder.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////


CEncoder::CEncoder()
	//maximum line size for base64 encoding is 76 as per spec. 
	//actual line size needs space for crlf + 0
	:m_iMaxLineSize(74)
	//input block size for encoding is 3
	,m_sBlockSize(3)
	//counters for comparison to above values
	,m_sBlockPos(0)
	,m_iLinePos(0)
	//probably static?
	,m_pcMatrix("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
	//streams for reading and writing
	,m_pTargetStream(0)
	,m_pReadStream(0)
{

}

CEncoder::~CEncoder()
{
	//flush and release the streams
	closeCurrentStream();
}

void CEncoder::putc(char c)
{
	m_inputBlock[m_sBlockPos] = c;
	
	//if we have jumped over a block boundary, 
	// run the block routine. and set position to 
	//	0 again.
	if(++m_sBlockPos > m_sBlockSize)
	{
		encodeblock(m_sBlockSize);
		//check if writing would go over a 
		//	line boundary during the next write, 		
		if(m_iLinePos + 4 >= m_iMaxLineSize)
		{
			//if so. 
			// write crlf
			m_pTargetStream->Write("\n\r",2,0);
			//	reset the linepos counter
			m_iLinePos = 0;
		}
	}
}



//encode 3 8-bit binary bytes as 4 '6-bit' characters
void CEncoder::encodeblock(const short len )
{
/*
	a a a a a a a a|a a a a a a a a|a a a a a a a a
	->
	a a a a a a|a a a a a a|a a a a a a|a a a a a a
*/
	char out[4];
	//shift the 2  rightmost bits off the end of the first byte and encode the remainder
    out[0] = m_pcMatrix[ m_inputBlock[0] >> 2 ];
	
	//bit-or the 2 rightmost bits of the first byte with the first 4 of the second
	//	both shifted into the appropriate place.
    out[1] = m_pcMatrix[ ((m_inputBlock[0] & 0x03) << 4) | ((m_inputBlock[1] & 0xf0) >> 4) ];
	
	if(len > 1 )
	{
		//The third chunk is the last four bits of the second byte 
		//	plus the first two of the third byte
		out[2] = m_pcMatrix[ ((m_inputBlock[1] & 0x0f) << 2) | ((m_inputBlock[2] & 0xc0) >> 6) ] ;
	
		if(len > 2)
		{
			// the last chunk is just the 6 rightmost bit of the last byte
			out[3] = m_pcMatrix[ m_inputBlock[2] & 0x3f];
		}
		else
		{
			//pad the output string
			out[3] = '=';
		}
	}
	else
	{
		//pad the output string
		out[2] = '=';
		out[3] = '=';
	}


	if(m_pTargetStream)
	{
		//write it to the stream.
		m_pTargetStream->Write(out,4,0);
	}
	else
	{
		//TODO: this is an error condition
	}
	// increment the linepos count
	m_iLinePos +=4;

	//reset the block cursor.
	m_sBlockPos = 0;

}

void CEncoder::put_stream(IStream *pStream)
{
	//close the existing stream
	closeCurrentStream();

	m_pTargetStream = pStream;

	// could be putting null, just to close.
	if(m_pTargetStream)
	{
		m_pTargetStream->AddRef();
	}
}

void CEncoder::closeCurrentStream()
{
	//flush the pending bytes
	if(m_sBlockPos)
		encodeblock(m_sBlockPos);

	//release the stream.
	m_pTargetStream->Release();
	m_pTargetStream = 0;
	m_pReadStream->Release();
	m_pReadStream = 0;
	//reset the line position cursor,
	//	we are now starting at bof
	//	in the new stream.

	//TODO: something else may have written to the
	//	stream before us.  This means that the first line does 
	//	not neccessarily start at bof
	//	Is this our problem?
	m_iLinePos = 0;
}

HRESULT CEncoder::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
	HRESULT hr;
	if(m_pReadStream)
	{
		//if we have a read stream, read from it.
		hr = m_pReadStream->Read(pv,cb,pcbRead);
	}
	else if(m_pTargetStream)
	{
		//if we have a write stream, clone it into a read stream.
		hr = m_pTargetStream->Clone(&m_pReadStream);
		if(SUCCEEDED(hr))
		{
			LARGE_INTEGER l;
			l.QuadPart = 0;
			hr = m_pReadStream->Seek(l,0,0);
			if(SUCCEEDED(hr))
			{
				hr = m_pReadStream->Read(pv,cb,pcbRead);
			}
		}
	}
	else
	{
		hr = E_FAIL;
	}
	return hr;
}
